/*
 * AStarPathFinding Library - Copyright (c) Muhammad Ahmed - quteahmed@gmail.com
 * 
 * This software is provided 'as-is', without any express or
 * implied warranty. In no event will the authors be held
 * liable for any damages arising from the use of this software.
 * 
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute
 * it freely, subject to the following restrictions:
 * 
 * 1. The origin of this software must not be misrepresented;
 * you must not claim that you wrote the original software.
 * If you use this software in a product, an acknowledgment
 * in the product documentation would be appreciated but
 * is not required.
 * 
 * 2. Altered source versions must be plainly marked as such,
 * and must not be misrepresented as being the original software.
 * 
 * 3. This notice may not be removed or altered from any
 * source distribution.
 */
package com.thug.ai;

import java.util.ArrayList;

import org.anddev.andengine.entity.primitive.Line;
import org.anddev.andengine.entity.primitive.Rectangle;

import android.util.Log;

import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.QueryCallback;
import com.badlogic.gdx.physics.box2d.RayCastCallback;
import com.badlogic.gdx.physics.box2d.World;
import com.thug.ai.pathfinding.Node;
import com.thug.ai.pathfinding.NodeMap;
import com.thug.ai.pathfinding.NodeMap.HeuristicType;
import com.thug.ai.pathfinding.PathFinder;
import com.thug.engine.BaseGameEntity;
import com.thug.engine.BaseGameEntity.EntityType;
import com.thug.engine.SceneManager;
import com.thug.engine.Utility;

public class StageMap {

	final private SceneManager mSceneMgr;
	private float mMapStartPointX;
	private float mMapStartPointY;
	private float mMapEndPointX;
	private float mMapEndPointY;

	private float mStepResX;
	private float mStepResY;

	private int mWidth;
	private int mHeight;

	private float mInitialCost;

	private NodeMap mNodeMap;
	private Node mCurrentNode;

	private QueryCallback mQueryCallback;
	private RayCastCallback mRayCastCallback;
	private boolean mWalkable; 


	public StageMap(final SceneManager scnMgr, float startPosX, float startPosY, float stopPosX, float stopPosY, int cols, int rows, float initialCost) {
		mSceneMgr = scnMgr;

		mMapStartPointX = startPosX;
		mMapStartPointY = startPosY;
		mMapEndPointX = stopPosX;
		mMapEndPointY = stopPosY;
		mWidth = cols;
		mHeight = rows;
		mInitialCost = initialCost;

		mStepResX = (mMapEndPointX - mMapStartPointX) / mWidth;
		mStepResY = (mMapEndPointY - mMapStartPointY) / mHeight;

		mNodeMap = new NodeMap(mWidth, mHeight, mInitialCost);
		mNodeMap.setHeuristicType(HeuristicType.Diagonal);

		mQueryCallback = new QueryCallback() {

			@Override
			public boolean reportFixture(Fixture fixture) {
				BaseGameEntity entity = (BaseGameEntity) fixture.getBody().getUserData();
				if(entity != null) {
					if((entity.getType() == EntityType.ET_WALL) || (entity.getType() == EntityType.ET_OBSTACLE)) {
						mCurrentNode.setCost(0);
						return false;
					}
				}
				mCurrentNode.setCost(mInitialCost);
				return true;
			}
		};

		mRayCastCallback = new RayCastCallback() {

			@Override
			public float reportRayFixture(Fixture fixture, Vector2 point, Vector2 normal, float fraction) {
				BaseGameEntity entity = (BaseGameEntity) fixture.getBody().getUserData();
				if(entity != null) {
					if((entity.getType() == EntityType.ET_WALL) || (entity.getType() == EntityType.ET_OBSTACLE)) {
						mWalkable = false;
						return 0;
					}
				}
				return 1;
			}
		};

	}


	/**
	 * reset all to zero
	 */
	public void refreshStageMap() {
		for (int X = 0; X < mWidth; X++)
		{
			for (int Y = 0; Y < mHeight; Y++)
			{
				mCurrentNode = mNodeMap.getNode(X, Y);
				mCurrentNode.onClosedList = false;
				mCurrentNode.onOpenList = false;
				mCurrentNode.setG(0);
				mCurrentNode.setH(0);
			}
		}
	}
	
	/**
	 * update the map as walkable and non walkable areas
	 */
	public void updateWalkableAreas(){
		Vector2 tileStart = new Vector2();
		Vector2 tileEnd = new Vector2();
		for (int X = 0; X < mWidth; X++)
		{
			for (int Y = 0; Y < mHeight; Y++)
			{
				mCurrentNode = mNodeMap.getNode(X, Y);
				mCurrentNode.setCost(mInitialCost);
				getSceneTile(mCurrentNode, tileStart, tileEnd);
				tileStart = Utility.scenetoWorldCoord(tileStart);
				tileEnd = Utility.scenetoWorldCoord(tileEnd);
				mSceneMgr.getPhysicsWorld().QueryAABB(mQueryCallback, tileStart.x, tileStart.y, tileEnd.x, tileEnd.y);
			}
		}
	}


	/**
	 * return path or null if path not found
	 * @param start
	 * @param goal
	 * @return
	 */
	public ArrayList<Vector2> findPath(Vector2 start, Vector2 goal) {
		refreshStageMap();
		Node startNode = getNode(start);
		Node finishNode = getNode(goal);
		ArrayList<Vector2> points = new ArrayList<Vector2>();
		boolean result = PathFinder.AStar(mNodeMap, startNode, finishNode);
		if(result) {
			while (finishNode != startNode)
			{

				Vector2 point = getScenePoint(finishNode);
				points.add(point);
				finishNode = finishNode.getParent();
				//if(finishNode == null) break;
			}

			//
			displayPath(points, 1, 0, 0, 0.3f);
			Log.d("Stage Map " , "Before smoothing " + points.size());
			smoothPath(points);
			Log.d("Stage Map " , "After smoothing " + points.size());
			displayPath(points, 0, 1, 0, 0.3f);

			return points;
		}
		return null;

	}

	/**
	 * get the scene point by giving the node
	 * @param node
	 * @return
	 */
	public Vector2 getScenePoint(Node node) {
		Vector2 v = new Vector2();		
		v.x = (mStepResX * node.getX()) + mMapStartPointX;
		v.y = (mStepResY * node.getY()) + mMapStartPointY;
		return v;
	}

	/**
	 * get node by giving the scene point
	 * @param point
	 * @return
	 */
	public Node getNode(Vector2 point) {
		int x, y;
		x = (int) ((int) (point.x - mMapStartPointX) / mStepResX);
		y = (int) ((int) (point.y - mMapStartPointY) / mStepResY);
		return mNodeMap.getNode(x, y);
	}

	/**
	 * get scene tile by giving the node
	 * @param node in
	 * @param tileStartPos out
	 * @param tileEndPos out
	 */
	public void getSceneTile(Node node, Vector2 tileStartPos, Vector2 tileEndPos) {
		Vector2 point = getScenePoint(node);

		tileStartPos.x = point.x - (mStepResX * 0.5f);
		tileEndPos.x = point.x + (mStepResX * 0.5f);

		tileStartPos.y = point.y - (mStepResY * 0.5f);
		tileEndPos.y = point.y + (mStepResY * 0.5f);
	}

	//TODO: debug purpose only
	public void drawMap() {
		Vector2 tileStart = new Vector2();
		Vector2 tileEnd = new Vector2();

		for (int X = 0; X < mWidth; X++)
		{
			for (int Y = 0; Y < mHeight; Y++)
			{
				mCurrentNode = mNodeMap.getNode(X, Y);
				getSceneTile(mCurrentNode, tileStart, tileEnd);
				Rectangle r = new Rectangle(tileStart.x, tileStart.y, tileEnd.x - tileStart.x, tileEnd.y - tileStart.y);
				r.setColor(0.2f, 0.3f, 0.4f, (float)mCurrentNode.getCost());
				mSceneMgr.getScene().attachChild(r);
				//Log.d("Stage Map : ", "Rect : " + tileStart.x + ", " + tileStart.y + ", " + tileEnd.x + ", " + tileEnd.y);
			}
		}
	}


	//smooth
	private void smoothPath(ArrayList<Vector2> path) {
		/*
		 * checkPoint = starting point of path
		 * currentPoint = next point in path
		 * while (currentPoint->next != NULL)
		 * if Walkable(checkPoint, currentPoint->next)
		 * Make a straight path between those points:
		 * temp = currentPoint
		 * currentPoint = currentPoint->next
		 * delete temp from the path
		 * else
		 * checkPoint = currentPoint
		 * currentPoint = currentPoint->next
		 */

		World world = mSceneMgr.getPhysicsWorld().getBodies().next().getWorld();
		Vector2 phyCheckPoint;
		Vector2 phyCurrentPoint;
		Vector2 checkPoint = path.get(0);
		Vector2 currentPoint = path.get(1);
		for(int i = 1; i < path.size() - 1; i++) {
			mWalkable = true;
			phyCheckPoint = Utility.scenetoWorldCoord(checkPoint);
			phyCurrentPoint = Utility.scenetoWorldCoord(currentPoint);
			world.rayCast(mRayCastCallback , phyCheckPoint, phyCurrentPoint);
			if(mWalkable) {
				currentPoint = path.get(i+1);
				path.remove(i);
				//i--;
			}else{
				checkPoint = currentPoint;
				currentPoint = path.get(i+1);
			}
		}
	}


	/**
	 * draw a given path in the scene
	 * @param path
	 * @param red
	 * @param green
	 * @param blue
	 * @param alpha
	 */
	private void displayPath(ArrayList<Vector2> path, float red, float green, float blue, float alpha) {
		int count = path.size() - 1;
		for(int i = 0; i < count; i++) {
			Vector2 beg = path.get(i);
			Vector2 end = path.get(i+1);

			Line line = new Line(beg.x, beg.y, end.x, end.y);
			line.setLineWidth(3);
			line.setColor(red, green, blue, alpha);
			mSceneMgr.getScene().attachChild(line);
		}
	}


}
